/*
 * Copyright 2025 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 *
 * This file contains private enums related to paths. See also skbug.com/40042016
 */

#include "include/core/SkPath.h"
#include "include/core/SkPathTypes.h"
#include "include/core/SkSpan.h"
#include "src/core/SkPathEnums.h"
#include "src/core/SkPathPriv.h"

#include <algorithm>
#include <cmath>
#include <cstring>
#include <limits.h>
#include <utility>

/*  Contains path methods that require SkPathData -- not SkPathRef:
 *
 *  The remaining fields:
 *  - fFillType
 *  - fIsVolatile
 *
 *  ... are shared in both implemtations.
 */

#ifdef SK_PATH_USES_PATHDATA

/*
 *  This returns a singleton instance which SkPath uses to signify that its pathdata is in error:
 *  either because the inputs were invalid (e.g. bad verbs), or its coordintes were non-finite
 *  (either from the client, or after a makeTransform() call).
 */
SkPathData* SkPath::PeekErrorSingleton() {
    static SkPathData* gErrorSingleton = SkPathData::MakeNoCheck({}, {}, {}, {}, {}).release();

    // Make sure MakeNoCheck() didn't alias us to the standard Empty instance. We want our
    // pointer to be distinct from that one.
    SkASSERT(gErrorSingleton != SkPathData::Empty().get());

    return gErrorSingleton;
}

SkPath SkPath::MakeNullCheck(sk_sp<SkPathData> pdata, SkPathFillType ft, bool isVolatile) {
    if (!pdata) {
        pdata = sk_ref_sp(PeekErrorSingleton());
    }
    return SkPath(std::move(pdata), ft, isVolatile);
}

//////////////////////////////////////////////////////////////////////////////////////////////////

SkPath::SkPath(sk_sp<SkPathData> pd, SkPathFillType ft, bool isVolatile)
    : fPathData(std::move(pd))
    , fFillType(ft)
    , fIsVolatile(isVolatile)
{
    SkASSERT(fPathData);
}

SkPath::SkPath(SkPathFillType ft)
    : fPathData(SkPathData::Empty())
    , fFillType(ft)
    , fIsVolatile(false)
{}

SkPath::SkPath(const SkPath& that)
    : fPathData(that.fPathData)
    , fFillType(that.fFillType)
    , fIsVolatile(that.fIsVolatile)
{}

SkPath& SkPath::operator=(const SkPath& o) {
    if (this != &o) {
        fPathData   = o.fPathData;
        fFillType   = o.fFillType;
        fIsVolatile = o.fIsVolatile;
    }
    return *this;
}

void SkPath::setConvexity(SkPathConvexity c) const {
    fPathData->setConvexity(c);
}

SkPathConvexity SkPath::getConvexityOrUnknown() const {
    return fPathData->getConvexityOrUnknown();
}

bool operator==(const SkPath& a, const SkPath& b) {
    return &a == &b ||
          (a.fFillType == b.fFillType && *a.fPathData == *b.fPathData);
}

void SkPath::swap(SkPath& that) {
    if (this != &that) {
        fPathData.swap(that.fPathData);
        std::swap(fFillType, that.fFillType);
        std::swap(fIsVolatile, that.fIsVolatile);
    }
}

SkPath& SkPath::reset() {
    *this = SkPath();
    return *this;
}

const SkRect& SkPath::getBounds() const {
    return fPathData->bounds();
}

uint32_t SkPath::getSegmentMasks() const {
    return fPathData->segmentMask();
}

bool SkPath::isFinite() const {
    return fPathData.get() != PeekErrorSingleton();
}

bool SkPath::isValid() const { return this->isFinite(); }

bool SkPath::hasComputedBounds() const { return true; }

uint32_t SkPath::getGenerationID() const { return fPathData->uniqueID(); }

#ifdef SK_DEBUG
void SkPath::validate() const {}

void SkPath::validateRef() const {}
#endif

std::optional<SkPathOvalInfo> SkPath::getOvalInfo() const { return fPathData->asOval(); }
std::optional<SkPathRRectInfo> SkPath::getRRectInfo() const { return fPathData->asRRect(); }

SkSpan<const SkPoint> SkPath::points() const { return fPathData->points(); }
SkSpan<const SkPathVerb> SkPath::verbs() const { return fPathData->verbs(); }
SkSpan<const float> SkPath::conicWeights() const { return fPathData->conics(); }

//////////////////////////////////////////////////////////////////////////////////////////////////

SkPath SkPath::Raw(SkSpan<const SkPoint> pts, SkSpan<const SkPathVerb> vbs,
                   SkSpan<const float> ws, SkPathFillType ft, bool isVolatile) {
    return MakeNullCheck(SkPathData::Make(pts, vbs, ws), ft, isVolatile);
}

SkPath SkPath::Rect(const SkRect& r, SkPathFillType ft, SkPathDirection dir, unsigned startIndex) {
    startIndex &= 3;    // keep it legal
    return MakeNullCheck(SkPathData::Rect(r, dir, startIndex), ft, false);
}

SkPath SkPath::Oval(const SkRect& r, SkPathDirection dir, unsigned startIndex) {
    startIndex &= 3;    // keep it legal
    return MakeNullCheck(SkPathData::Oval(r, dir, startIndex), SkPathFillType::kDefault, false);
}

SkPath SkPath::RRect(const SkRRect& rr, SkPathDirection dir, unsigned startIndex) {
    startIndex &= 7;    // keep it legal
    // To be backwards compatible with the old impl for building a rrect path, we
    // first check to see if the rrect itself can be simplified...
    const SkRect& bounds = rr.getBounds();
    auto [asType, newIndex] = SkPathPriv::SimplifyRRect(rr, startIndex);
    switch (asType) {
        case SkPathPriv::RRectAsEnum::kRect:
            return SkPath::Rect(bounds, SkPathFillType::kDefault, dir, newIndex);

        case SkPathPriv::RRectAsEnum::kOval:
            return SkPath::Oval(bounds, dir, newIndex);

        case SkPathPriv::RRectAsEnum::kRRect:
            // fall through
            break;
    }
    return MakeNullCheck(SkPathData::RRect(rr, dir, newIndex), SkPathFillType::kDefault, false);
}

SkPath SkPath::Polygon(SkSpan<const SkPoint> pts, bool isClosed,
                       SkPathFillType ft, bool isVolatile) {
    return MakeNullCheck(SkPathData::Polygon(pts, isClosed), ft, isVolatile);
}

std::optional<SkPath> SkPath::tryMakeTransform(const SkMatrix& matrix) const {
    if (auto pdata = fPathData->makeTransform(matrix)) {
        return SkPath(std::move(pdata), fFillType, fIsVolatile);
    }
    return {};
}

SkPath SkPath::makeTransform(const SkMatrix& matrix) const {
    if (!this->isFinite()) {
        return *this;
    }
    if (auto newpath = this->tryMakeTransform(matrix)) {
        return *newpath;
    }
    return SkPath(sk_ref_sp(PeekErrorSingleton()), fFillType, false);
}

std::optional<SkPathRaw> SkPath::raw(SkResolveConvexity rc) const {
    return fPathData->raw(fFillType, rc);
}

//////////////////////////////////////////////////////////////////////////////////////////////

bool SkPathPriv::TestingOnly_unique(const SkPath& path) {
    return path.fPathData->unique();
}

int SkPathPriv::GenIDChangeListenersCount(const SkPath& path) {
    return path.fPathData->genIDChangeListenerCount();
}

void SkPathPriv::AddGenIDChangeListener(const SkPath& path, sk_sp<SkIDChangeListener> listener) {
    auto pdata = path.fPathData.get();
    // SkPath's error-singleton is never deleted, so we don't want to add any listeners to it.
    if (pdata != SkPath::PeekErrorSingleton()) {
        pdata->addGenIDChangeListener(std::move(listener));
    }
}

//////////////////////////////////////////////////////////////////////////////////////////////

SkPathBuilder& SkPathBuilder::operator=(const SkPath& src) {
    this->reset().setFillType(src.getFillType());
    this->setIsVolatile(src.isVolatile());

    if (src.isEmpty()) {
        return *this;
    }

    this->addRaw(src.fPathData->raw(src.getFillType(), SkResolveConvexity::kYes));

    // These are not part of SkPathRaw, so we set them separately
    fLastMoveIndex = SkPathPriv::FindLastMoveToIndex(fVerbs, fPts.size());
    SkASSERT(fLastMoveIndex < fPts.size());
    fType = src.fPathData->fType;
    fIsA  = src.fPathData->fIsA;

    return *this;
}

SkPath SkPathBuilder::snapshot(const SkMatrix* mx) const {
    if (!mx) {
        mx = &SkMatrix::I();
    }

    sk_sp<SkPathData> pdata;
    if (auto raw = SkPathPriv::Raw(*this, SkResolveConvexity::kNo)) {
        pdata = SkPathData::MakeTransform(*raw, *mx);
    }
    if (pdata && fType != SkPathIsAType::kGeneral) {
        pdata->setupIsA(fType, fIsA.fDirection, fIsA.fStartIndex);
    }
    return SkPath::MakeNullCheck(std::move(pdata), fFillType, fIsVolatile);
}

#endif
